/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: Message queue
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2023-09-04     JasonHu           Init
 */

#include <base/msgqueue.h>
#include <base/malloc.h>
#include <base/memory.h>
#include <base/log.h>

typedef struct NX_MsgEntry
{
    NX_List list;
    NX_MsgBuf buf;
} NX_MsgEntry;

NX_INLINE NX_Bool NX_MsgBufIsInvalid(NX_MsgBuf *buf)
{
    return (buf->payload == NX_NULL) || (buf->len == 0);
}

void NX_MsgBufCopy(NX_MsgBuf *dest, NX_MsgBuf *src)
{
    dest->len = src->len;
    NX_MemCopy(dest->payload, src->payload, src->len);
}

NX_MsgEntry *NX_MsgEntryCreateFromBuf(NX_MsgBuf *buf)
{
    NX_MsgEntry *entry;

    entry = NX_MemAlloc(sizeof(NX_MsgEntry));
    if (entry == NX_NULL)
    {
        return NX_NULL;
    }

    NX_ListInit(&entry->list);
    
    entry->buf.payload = NX_MemAlloc(buf->len);
    if (entry->buf.payload == NX_NULL)
    {
        NX_MemFree(entry);
        return NX_NULL;
    }

    NX_MsgBufCopy(&entry->buf, buf);

    return entry;
}

NX_Error NX_MsgEntryDestroy(NX_MsgEntry *entry)
{
    if (entry == NX_NULL)
    {
        return NX_EINVAL;
    }
    NX_MemFree(entry->buf.payload);
    NX_MemFree(entry);
    return NX_EOK;
}

NX_MsgQueue *NX_MsgQueueCreate(NX_Size msgSize, NX_Size msgCount)
{
    NX_MsgQueue *msgQueue;

    if (msgSize == 0 || msgSize > NX_MSG_QUEUE_SIZE_MAX || 
        msgCount == 0 || msgCount > NX_MSG_QUEUE_COUNT_MAX)
    {
        return NX_NULL;
    }

    msgQueue = NX_MemAlloc(sizeof(NX_MsgQueue));
    if (msgQueue != NX_NULL)
    {
        msgQueue->msgCountMax = msgCount;
        msgQueue->msgSizeMax = msgSize;
        NX_ListInit(&msgQueue->queueListHead);
        NX_SpinInit(&msgQueue->queueLock);
        NX_SemaphoreInit(&msgQueue->msgSend, 0);
        NX_SemaphoreInit(&msgQueue->msgRecv, msgQueue->msgCountMax);
    }
    return msgQueue;
}

NX_Error NX_MegQueueDestroy(NX_MsgQueue *msgQueue)
{
    NX_MsgEntry *entry, *next;
    NX_UArch level;

    if (msgQueue == NX_NULL)
    {
        return NX_EINVAL;
    }

    NX_SemaphoreSignalAll(&msgQueue->msgSend);
    NX_SemaphoreSignalAll(&msgQueue->msgRecv);

    NX_SpinLockIRQ(&msgQueue->queueLock, &level);
    NX_ListForEachEntrySafe(entry, next, &msgQueue->queueListHead, list)
    {
        NX_ListDelInit(&entry->list);
        NX_MsgEntryDestroy(entry);
    }
    NX_SpinUnlockIRQ(&msgQueue->queueLock, level);

    NX_MemFree(msgQueue);
    return NX_EOK;
}  

NX_Error NX_MsgQueueSend(NX_MsgQueue *msgQueue, NX_MsgBuf *buf)
{
    NX_Error err;
    NX_MsgEntry *entry;
    NX_UArch level;

    if (msgQueue == NX_NULL || buf == NX_NULL || NX_MsgBufIsInvalid(buf) == NX_True)
    {
        return NX_EINVAL;   
    }

    entry = NX_MsgEntryCreateFromBuf(buf);
    if (entry == NX_NULL)
    {
        return NX_ENOMEM;
    }

    /* wait msg qeueu if recv entry empty */
    err = NX_SemaphoreWait(&msgQueue->msgRecv);
    if (err != NX_EOK)
    {
        NX_MsgEntryDestroy(entry);
        return err;
    }
    
    /* lock and add del */
    err = NX_SpinLockIRQ(&msgQueue->queueLock, &level);
    if (err != NX_EOK)
    {
        NX_SemaphoreSignal(&msgQueue->msgRecv);
        NX_MsgEntryDestroy(entry);
        return err;
    }

    NX_ListAdd(&entry->list, &msgQueue->queueListHead);

    NX_SpinUnlockIRQ(&msgQueue->queueLock, level);

    /* make sure send a msg buf */
    err = NX_SemaphoreSignal(&msgQueue->msgSend);

    return err;
}

NX_Error NX_MsgQueueTrySend(NX_MsgQueue *msgQueue, NX_MsgBuf *buf)
{
    NX_Error err;
    NX_MsgEntry *entry;
    NX_UArch level;

    if (msgQueue == NX_NULL || buf == NX_NULL || NX_MsgBufIsInvalid(buf) == NX_True)
    {
        return NX_EINVAL;   
    }

    entry = NX_MsgEntryCreateFromBuf(buf);
    if (entry == NX_NULL)
    {
        return NX_ENOMEM;
    }

    /* wait msg qeueu if recv entry empty */
    err = NX_SemaphoreTryWait(&msgQueue->msgRecv);
    if (err != NX_EOK)
    {
        NX_MsgEntryDestroy(entry);
        return err;
    }
    
    /* lock and add del */
    err = NX_SpinTryLockIRQ(&msgQueue->queueLock, &level);
    if (err != NX_EOK)
    {
        NX_SemaphoreSignal(&msgQueue->msgRecv);
        NX_MsgEntryDestroy(entry);
        return err;
    }

    NX_ListAdd(&entry->list, &msgQueue->queueListHead);

    NX_SpinUnlockIRQ(&msgQueue->queueLock, level);

    /* make sure send a msg buf */
    err = NX_SemaphoreSignal(&msgQueue->msgSend);

    return err;
}

NX_Error NX_MsgQueueRecv(NX_MsgQueue *msgQueue, NX_MsgBuf *buf)
{
    NX_Error err;
    NX_MsgEntry *entry;
    NX_UArch level;

    if (msgQueue == NX_NULL || buf == NX_NULL || NX_MsgBufIsInvalid(buf) == NX_True)
    {
        return NX_EINVAL;   
    }

    /* wait msg if send entry empty */
    err = NX_SemaphoreWait(&msgQueue->msgSend);
    if (err != NX_EOK)
    {
        return err;
    }

    /* lock and add del */
    err = NX_SpinLockIRQ(&msgQueue->queueLock, &level);
    if (err != NX_EOK)
    {
        NX_SemaphoreSignal(&msgQueue->msgSend);
        return err;
    }
    entry = NX_ListFirstEntryOrNULL(&msgQueue->queueListHead, NX_MsgEntry, list);
    NX_ListDelInit(&entry->list);
    
    NX_SpinUnlockIRQ(&msgQueue->queueLock, level);

    /* copy buf info */
    NX_MsgBufCopy(buf, &entry->buf);

    NX_MsgEntryDestroy(entry);

    /* make sure recv a msg buf */
    err = NX_SemaphoreSignal(&msgQueue->msgRecv);

    return err;
}

NX_Error NX_MsgQueueTryRecv(NX_MsgQueue *msgQueue, NX_MsgBuf *buf)
{
    NX_Error err;
    NX_MsgEntry *entry;
    NX_UArch level;

    if (msgQueue == NX_NULL || buf == NX_NULL || NX_MsgBufIsInvalid(buf) == NX_True)
    {
        return NX_EINVAL;   
    }

    /* wait msg if send entry empty */
    err = NX_SemaphoreTryWait(&msgQueue->msgSend);
    if (err != NX_EOK)
    {
        return err;
    }

    /* lock and add del */
    err = NX_SpinTryLockIRQ(&msgQueue->queueLock, &level);
    if (err != NX_EOK)
    {
        NX_SemaphoreSignal(&msgQueue->msgSend);
        return err;
    }
    entry = NX_ListFirstEntryOrNULL(&msgQueue->queueListHead, NX_MsgEntry, list);
    NX_ListDelInit(&entry->list);
    
    NX_SpinUnlockIRQ(&msgQueue->queueLock, level);

    /* copy buf info */
    NX_MsgBufCopy(buf, &entry->buf);

    NX_MsgEntryDestroy(entry);

    /* make sure recv a msg buf */
    err = NX_SemaphoreSignal(&msgQueue->msgRecv);

    return err;
}

NX_Bool NX_MsgQueueFull(NX_MsgQueue *msgQueue)
{
    if (msgQueue == NX_NULL)
    {
        return NX_False;
    }
    return (NX_SemaphoreGetValue(&msgQueue->msgSend) == msgQueue->msgCountMax) ? NX_True : NX_False;
}

NX_Bool NX_MsgQueueEmpty(NX_MsgQueue *msgQueue)
{
    if (msgQueue == NX_NULL)
    {
        return NX_False;
    }
    return (NX_SemaphoreGetValue(&msgQueue->msgSend) == 0) ? NX_True : NX_False;
}

NX_Size NX_MsgQueueLenght(NX_MsgQueue *msgQueue)
{
    return NX_SemaphoreGetValue(&msgQueue->msgSend);
}
